package com.example.mobiledatapackage.service;

import com.example.mobiledatapackage.domain.Package;
import com.example.mobiledatapackage.domain.WeakSpot;
import com.example.mobiledatapackage.domain.pojo.PackageRecommendQueryCondDto;
import com.example.mobiledatapackage.mapper.PackageMapper;
import com.example.mobiledatapackage.ontology.OntProp;
import com.example.mobiledatapackage.ontology.RdfInstances;
import com.example.mobiledatapackage.request.UserStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.NodeIterator;
import org.apache.jena.rdf.model.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class RecommendService {

    @Autowired
    private PackageMapper packageMapper;

    @Autowired
    private Model rawModel;

    /**
     * 根据用户的状态和推理出来的可改进部分来推荐更适合用户的套餐
     * @param userStatus 用户的状态，包括套餐的使用情况
     * @param weakSpots 可改进的部分
     * @return 搜索到更加适合用户的业务套餐
     */
    public PackageRecommendQueryCondDto makeQueryCondByWeakSpots(UserStatus userStatus, List<WeakSpot> weakSpots) {
        Package currPkg = packageMapper.selectByPrimaryKey(userStatus.getPkgId());  // 用户当前用的套餐
        PackageRecommendQueryCondDto cond = new PackageRecommendQueryCondDto();  // cond：用来在 MySQL 做条件查询的各条件
        // 初始化 cond，设置各指标的最大值，防止推荐过于偏离用户期望的套餐（比如用户一个月用了 30M 流量，却推荐了一个每月免费 30G 流量的套餐）
        cond.setDomesticDataMaxLimit(Math.max(userStatus.getUsedDomesticData() * 5, currPkg.getHasFreeDomesticData() * 5));
        cond.setOverseaDataMaxLimit(Math.max(userStatus.getUsedOverseaData() * 5, currPkg.getHasFreeOverseaData() * 5));
        cond.setDomesticVoiceCallMaxLimit(Math.max(userStatus.getUsedDomesticVoiceCall() * 5, currPkg.getHasFreeDomesticVoiceCall() * 5));
        cond.setOverseaVoiceCallMaxLimit(Math.max(userStatus.getUsedOverseaVoiceCall() * 5, currPkg.getHasFreeDomesticVoiceCall() * 5));
        // 初始化 cond，设置各指标的最小值，最小值为当前已用的该指标的量（比如一个用户已经用了 30M 流量，那推荐的套餐肯定不能小于 30M 的流量）
        cond.setDomesticDataMinLimit(userStatus.getUsedDomesticData());
        cond.setNeedUnlimitedDomesticData(false);  // 初始情况下不需要无限流量
        cond.setOverseaDataMinLimit(userStatus.getUsedOverseaData());
        cond.setNeedUnlimitedOverseaData(false);
        cond.setDomesticVoiceCallMinLimit(userStatus.getUsedDomesticVoiceCall());
        cond.setNeedUnlimitedDomesticVoiceCall(false);
        cond.setOverseaVoiceCallMinLimit(userStatus.getUsedOverseaVoiceCall());
        cond.setNeedUnlimitedOverseaVoiceCall(false);
        // 针对每一个 weak spot 来设置 cond，从而选出更加适合用户的套餐
        for(WeakSpot w: weakSpots) {
            WeakSpotDealStrategy dealStrategy = getStrategyOfWeakSpot(w);
            if (dealStrategy != null)
                dealStrategy.apply(userStatus, cond, currPkg, w);
        }
        System.out.println(cond);
        return cond;
    }

    /**
     * 获取用来处理某个 weak spot 的 strategy
     * @param w 所要处理的 weak spot
     * @return 对应的 strategy
     */
    private WeakSpotDealStrategy getStrategyOfWeakSpot(WeakSpot w) {
        // 处理 weak spot 为的 property 为 likeAppSeries 的情况
        if (w.getProperty().equals(OntProp.likeAppSeries)) {
            return ((userStatus, cond, currPkg, weakSpot) -> {
                if (cond.getAppSeriesPreferences() == null)
                    cond.setAppSeriesPreferences(new ArrayList<>());
                Resource appSeries = weakSpot.getSpot().asResource();
                NodeIterator iter = rawModel.listObjectsOfProperty(appSeries, OntProp.hasId);
                if (!iter.hasNext()) {
                    log.warn(String.format("app_series 数据库中没有 %s 对应的数据", appSeries));
                    return;  // 表示数据库中没有对应的 app series，此时应跳过
                }
                String seriesId = iter.next().asLiteral().getString();
                System.out.println(seriesId);
                cond.getAppSeriesPreferences().add(seriesId);
            });
        }
        return mapWeakSpotToStrategy.get(w);
    }

    static private final Map<WeakSpot, WeakSpotDealStrategy> mapWeakSpotToStrategy = new HashMap<>() {{
        // 当需要更多的国内流量时
        put(new WeakSpot(OntProp.needMore, RdfInstances.domesticData),
                (userStatus, cond, currPkg, weakSpot) -> {
                    cond.setDomesticDataMinLimit(Math.max(userStatus.getUsedDomesticData() * 2, currPkg.getHasFreeDomesticData() * 2));
                    cond.setNeedUnlimitedDomesticData(true);
                });
        put(new WeakSpot(OntProp.needMore, RdfInstances.overseaData),
                (userStatus, cond, currPkg, weakSpot) -> {
                    cond.setOverseaDataMinLimit(Math.max(userStatus.getUsedOverseaData() * 2, currPkg.getHasFreeOverseaData() * 2));
                    cond.setNeedUnlimitedOverseaData(true);
                });
        // 当需要更多的语音时间时
        put(new WeakSpot(OntProp.needMore, RdfInstances.domesticCall),
                (userStatus, cond, currPkg, weakSpot) -> {
                    cond.setDomesticVoiceCallMinLimit(Math.max(userStatus.getUsedDomesticVoiceCall() * 2, currPkg.getHasFreeDomesticVoiceCall() * 2));
                    cond.setNeedUnlimitedDomesticVoiceCall(true);
                });
        put(new WeakSpot(OntProp.needMore, RdfInstances.overseaCall),
                (userStatus, cond, currPkg, weakSpot) -> {
                    cond.setOverseaVoiceCallMinLimit(Math.max(userStatus.getUsedOverseaVoiceCall() * 2, currPkg.getHasFreeOverseaVoiceCall() * 2));
                    cond.setNeedUnlimitedOverseaVoiceCall(true);
                });
    }};
}


/**
 * 根据 weak spot 采取相应的设置 cond 的策略接口
 */
@FunctionalInterface
interface WeakSpotDealStrategy {
    void apply(UserStatus userStatus, PackageRecommendQueryCondDto cond, Package currPkg, WeakSpot weakSpot);
}
